package calculator.parser;

import java.util.regex.MatchResult;

import javapop.framework.Grammar;
import javapop.framework.ParseContext;
import javapop.framework.Parser;
import javapop.framework.parser.Char;
import javapop.framework.parser.Choice;
import javapop.framework.parser.EOF;
import javapop.framework.parser.Forget;
import javapop.framework.parser.LineComment;
import javapop.framework.parser.Literal;
import javapop.framework.parser.Many;
import javapop.framework.parser.Regexp;
import javapop.framework.parser.Space;
import javapop.framework.parser.XForm;
import javapop.framework.parser.expr.ExprAssoc;
import javapop.framework.parser.expr.Expression;
import javapop.framework.parser.tuple.One;
import calculator.ast.Const;
import calculator.ast.Expr;
import calculator.ast.InfixOp;
import calculator.ast.PrefixOp;

public class CalculatorGrammar extends Grammar {

	public CalculatorGrammar() {
		createGrammar();
	}
	
	private void createGrammar() {
		Parser<Object> spaces = new Forget(new Many<Character>(Space.getInstance()));

		Parser<Object> lineComment = new Forget(new LineComment(new Literal("% ")));
		
		Parser<?> skip = new Many<Object>(
				new Choice<Object>()
				.either(spaces)
				.orElse(lineComment));
		
		Expression<Expr> expression = new Expression<Expr>("calculator-grammar")
		.skip(skip)
		.operand(new XForm<MatchResult,Const>(new Regexp("[0-9]+(\\.[0-9]+)?([eE]([+-])?[0-9]+)?")) {
			@Override
			public Const transform(ParseContext<?> ctx,MatchResult content) {
				double value = 0;
				try {
					value = Double.parseDouble(content.group());
				} catch(NumberFormatException e) {
					setError("Cannot parse number: "+content,true);
					return null;
				}
				return new Const(value);
			}
		})
		.bracket(new Char('('), new Char(')'))
		.infix(/* PRIO */ 40, /* ASSOC */ ExprAssoc.LEFT,
				new XForm<Character,InfixOp>(new Char('+')) {
			@Override
			public InfixOp transform(ParseContext<?> ctx,Character content) {
				return new InfixOp() {
					@Override
					public double eval() {
						return getLeftOperand().eval() + getRightOperand().eval();
					}
					
					@Override
					public String getDescription() {
						return "Infix:+";
					}
				};
			}			
		})
		.infix(/* PRIO */ 40, /* ASSOC */ ExprAssoc.LEFT,
				new XForm<Character,InfixOp>(new Char('-')) {
			@Override
			public InfixOp transform(ParseContext<?> ctx,Character content) {
				return new InfixOp() {
					@Override
					public double eval() {
						return getLeftOperand().eval() - getRightOperand().eval();
					}
					@Override
					public String getDescription() {
						return "Infix:-";
					}
				};
			}			
		})
		.infix(/* PRIO */ 60, /* ASSOC */ ExprAssoc.LEFT,
				new XForm<Character,InfixOp>(new Char('*')) {
			@Override
			public InfixOp transform(ParseContext<?> ctx,Character content) {
				return new InfixOp() {
					@Override
					public double eval() {
						return getLeftOperand().eval() * getRightOperand().eval();
					}

					@Override
					public String getDescription() {
						return "Infix:*";
					}
			};
			}			
		})
		.infix(/* PRIO */ 60, /* ASSOC */ ExprAssoc.LEFT,
				new XForm<Character,InfixOp>(new Char('/')) {
			@Override
			public InfixOp transform(ParseContext<?> ctx,Character content) {
				return new InfixOp() {
					@Override
					public double eval() {
						return getLeftOperand().eval() / getRightOperand().eval();
					}
					@Override
					public String getDescription() {
						return "Infix:/";
					}

				};
			}			
		});
		
		expression.prefix(80, new XForm<Character,PrefixOp>(new Char('-')) {

			@Override
			public PrefixOp transform(ParseContext<?> ctx,Character content) {
				return new PrefixOp() {

					@Override
					public double eval() {
						return - getOperand().eval();
					}

					@Override
					public String getDescription() {
						return "Prefix:-";
					}
					
				};
			}			
		});

		register("expression",expression);
				
		setMainParser(new One<Expr>().parser(expression).then(EOF.getInstance()));
	}
}
